package prefuse.data.parser;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;

import prefuse.data.column.Column;
import prefuse.data.event.ColumnListener;

//WEENY added
public class CollectionParser implements DataParser, ColumnListener{
	static Logger logger = Logger.getLogger(CollectionParser.class.getName());
	private DataParser elemParser = null;
	private Class collectionType = null; 

	public boolean canParse(String text) {
		throw new UnsupportedOperationException();
	}

	public CollectionParser(Class type){
//		if(Collection.class.isAssignableFrom(type)){
//			throw new IllegalArgumentException(type + " is not a collection");
//		}
		int modifier = type.getModifiers();
    	if(Modifier.isAbstract(modifier) || Modifier.isInterface(modifier))
       		throw new IllegalArgumentException(type + " is not concrete");
		this.collectionType = type;
	}
	
	/** 
	 * test if the parser is ready for using
	 */
	public boolean isReady(){
		if (collectionType == null || elemParser == null) {
			return false;
		}
		else if (elemParser instanceof CollectionParser){
			return ((CollectionParser) elemParser).isReady();
		}
		return true;
	}
	public DataParser getElementParser(){
		return this.elemParser;
	}
	public void setElemParser(DataParser elemParser){
		this.elemParser = elemParser;
	}
	
	public String format(Object value) {
		if (!isReady()){
			init(value);			
		}
		if (elemParser == null || value == null) {
			return "";
		}
		Collection coll = (Collection) value;
		String[] array = new String[coll.size()];
		int idx = 0;
		for (Object elem : coll){
			array[idx++] = elemParser.format(elem);
		}
		return string2Csv(array);
	}

	public Class getType() {
		return collectionType;
	}
	public Object parse(String text) throws DataParseException {
		if (!isReady()) {
			throw new DataParseException("CollectionParser is not ready");
		}
		List<String> elems = csv2List(text);
		try {
			Collection coll = (Collection) collectionType.newInstance();
			for (String elem : elems) {
				coll.add(elemParser.parse(elem));
			}
			return coll;
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return text;
	}
	
	//helper functions	
	public static String string2Csv(String[] strings){
		if (strings == null) {
			return null;
		}
		if (strings.length == 0) {
			return "";
		}
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < strings.length; i++) {
			if (i>0) sb.append(",");
			String s = strings[i];
			if ((  s.indexOf('\"')) >= 0 
				|| s.indexOf(',')  >= 0 
				|| s.indexOf('\n') >= 0 
				|| Character.isWhitespace(s.charAt(0)) 
				|| Character.isWhitespace(s.charAt(s.length()-1)) )
		    {
				sb.append('\"');
				for (int j = 0; j<s.length(); j++){
					if(s.charAt(j) == '\"')
						sb.append("\"\"");
					else
						sb.append(s.charAt(j));
				}
				sb.append('\"');
		    }
			else{
				sb.append(strings[i]);
			}
		}
		return sb.toString();
	}
	public static List<String> csv2List(String line){
		List<String> result = new ArrayList<String>();
        boolean inRecord = false;
        int inQuote  = 0;
        int lineno   = 0;
        int col      = 0;
        StringBuffer sbuf = new StringBuffer();
        
		char[] c = line.toCharArray();
        int last = c.length-1;
        
        // iterate through current line
        for ( int i=0; i<=last; ++i ) {
            if ( !inRecord ) {
                // not currently processing a record
                if ( Character.isWhitespace(c[i]) )
                {
                    continue;
                }
                else if ( c[i] == '\"' )
                {
                    inRecord = true;
                    inQuote  = 1;
                }
                else if ( c[i] == ',' )
                {
//                    String s = sbuf.toString().trim();
                	String s= sbuf.toString();
                    result.add(s);
                    sbuf.delete(0, sbuf.length());
                }
                else
                {
                    inRecord = true;
                    sbuf.append(c[i]);
                }
            } else {
                // in the midst of a record
                if ( inQuote == 1 ) {
                    if ( c[i]=='\"' && (i==last || c[i+1] != '\"') )
                    {
                        // end of quotation
                        inQuote = 2;
                    }
                    else if ( c[i]=='\"' )
                    {
                        // double quote so skip one ahead
                        sbuf.append(c[i++]);
                    }
                    else
                    {
                        sbuf.append(c[i]);
                    }
                } else {
                    if ( Character.isWhitespace(c[i]) )
                    {
                        sbuf.append(c[i]);
                    }
                    else if ( c[i] != ',' && inQuote == 2 )
                    {
                        throw new IllegalStateException(
                            "Invalid data format. " + 
                            "Error at line " + lineno + ", col " + i);
                    }
                    else if ( c[i] != ',' )
                    {
                        sbuf.append(c[i]);
                    }
                    else
                    {
//                        String s = sbuf.toString().trim();
                    	String s= sbuf.toString();
                        result.add(s);
                        sbuf.delete(0, sbuf.length());
                        inQuote = 0;
                        inRecord = false;
                    }
                }
            }
        }
        if ( inQuote != 1 ) {
//            String s = sbuf.toString().trim();
        	String s= sbuf.toString();
            result.add(s);
            sbuf.delete(0, sbuf.length());
            inQuote = 0;
            inRecord = false;
        }
        if ( !inRecord && col > 0 ) {
            col = 0;
        }
        return result;
	}

	
	//When the parser is created, it is not ready, 'cause we do not know how
	//to parse the elements, so we listen to the column until we find a good
	//instance and analyze it, and find the correct parser for the element,
	//Once we done analyzing the instance, we stop listening to the column
	//NOTE: here we assume all the elements have the same structure!
	public void columnChanged(Column src, int type, int start, int end) {   }
	public void columnChanged(Column src, int idx,     int prev) {/*ignore*/}
	public void columnChanged(Column src, int idx,    long prev) {/*ignore*/}
	public void columnChanged(Column src, int idx,   float prev) {/*ignore*/}
	public void columnChanged(Column src, int idx,  double prev) {/*ignore*/}
	public void columnChanged(Column src, int idx, boolean prev) {/*ignore*/}
	public void columnChanged(Column src, int idx,  Object prev) {
		Collection obj = (Collection) src.get(idx);
		if (obj == null || obj.isEmpty()) {
			return;
		}
		CollectionParser cp = analyze(obj);
		this.collectionType = cp.collectionType;
		this.elemParser = cp.elemParser;
		src.removeColumnListener(this);
	}
	public boolean init(Object obj){
		CollectionParser cp = analyze(obj);
		if (null == cp) {
			this.elemParser = null;
		}
		else {
			this.collectionType = cp.collectionType;
			this.elemParser = cp.elemParser;
		}
		return this.isReady();
	}
	public static CollectionParser analyze(Object obj){
		if (obj == null || !(obj instanceof Collection)) {
			return null;
		}
		Collection coll = (Collection) obj;
		CollectionParser cp = new CollectionParser(coll.getClass());
		for (Object elem : coll) {
			if (elem == null) {
				continue;
			}
			else if (elem instanceof Collection) {
				cp.elemParser = analyze((Collection) elem);
				if (cp.isReady()){
					//now likely we have a good element instance
					break;
				}
			}
			else{
				ParserFactory pf = ParserFactory.getDefaultFactory();
				DataParser parser = pf.getParser(elem.getClass());
				if (parser == null) {
					logger.warning("cannot parse: "+elem.getClass());
				}
				cp.elemParser = parser;
				break;
			}
		}
		return cp;
	}
	public boolean compatible(Class type) {
		//this is special, we need to think about its elements
		return false;
	}
}